跳到主要内容

Unity 编写一个简易的事件框架

事件系统的作用

事件系统的好处很多,最主要的还是能够最大限度的降低模块间的耦合度,这里实现一个很简单的事件管理系统,主要功能就是对事件进行分发,以达到解偶的目的。

编写事件类型枚举

定义一个枚举用来管理事件类型,如果想要定义不同的事件则需要在这个枚举里面加上,例如自定义了一个事件实体就需要在这里注册(事件实体看下面那一节)

namespace EventFrame
{
/// <summary>
/// 定义事件 id
///
/// 这里可以定义一个事件
/// </summary>
public enum EventID
{
EVENT_1 = 10001,
EVENT_2 = 10002,
}
}

创建一个事件消息实体基类

namespace EventFrame
{
public class EventData
{
public EventID eid;

public EventData(EventID eid)
{
this.eid = eid;
}

public void Send()
{
EventManager.instance.SendEvent(this);
}

public static EventData CreateEvent(EventID eventId)
{
return new EventData(eventId);
}
}
}

这里 EventData 是基础的消息类,一些不需要参数的消息可以直接使用,所有消息都需要继承 EventData 类。

定义一个监听者接口

namespace EventFrame
{
public interface IEventObserver
{
/// <summary>
/// 事件观察者接口,当有事件发过来时事件管理器会调用这个方法
/// </summary>
/// <param name="resp">发送过来的</param>
void HandleEvent(EventData resp);
}
}

所有的监听者都要实现 HandleEvent 方法。

定义一个事件管理器

这里 Singleton 的工具类是之前那篇单例工具类那节创建的

虽然它是单例对象,但是还是要在使用了 instance 的静态方法里面加上

if (EventManager.instance == null) return;

因为它还是有可能因为各种原因被销毁(比如游戏结束)

namespace EventFrame
{
/// <summary>
/// 事件的管理器
/// </summary>
public class EventManager : Singleton<EventManager>
{
/// <summary>
/// 维护一个观察者队列
/// </summary>
private readonly Dictionary<EventID, List<IEventObserver>> observerList =
new Dictionary<EventID, List<IEventObserver>>();

private readonly Queue<EventData> eventQueue = new Queue<EventData>(); //消息队列

private void Awake()
{
Debug.Log("===========启动消息系统===========");
}

private void Update()
{
while (eventQueue.Count > 0)
{
// 从队列弹出事件
EventData eve = eventQueue.Dequeue();

// 如果没有观察者监听这个事件则继续下一个事件的分发
if (!observerList.ContainsKey(eve.eid)) continue;

// 通知监听了这个事件的全部观察者
List<IEventObserver> observers = observerList[eve.eid];
for (int i = 0; i < observers.Count; i++)
{
if (observers[i] == null) continue;
observers[i].HandleEvent(eve);
}
}
}

/// <summary>
/// 发送事件
/// </summary>
/// <param name="eve"></param>
public void SendEvent(EventData eve)
{
eventQueue.Enqueue(eve);
}

/// <summary>
/// 注册一个监听者
/// </summary>
/// <param name="newobj">需要注册的监听者</param>
/// <param name="eid">需要监听的事件 ID</param>
private void RegisterObj(IEventObserver newobj, EventID eid)
{
if (!observerList.ContainsKey(eid))
{
var list = new List<IEventObserver> {newobj};
observerList.Add(eid, list);
}
else
{
List<IEventObserver> list = observerList[eid];
// 检查当前是否已经存在,如果存在跳过注册
foreach (IEventObserver obj in list)
{
if (obj == newobj)
{
return;
}
}
// 不存在则注册
list.Add(newobj);
}
}

/// <summary>
/// 移除监听者
/// </summary>
/// <param name="removeObj">需要移除的监听对象</param>
private void RemoveObj(IEventObserver removeObj)
{
foreach (KeyValuePair<EventID, List<IEventObserver>> kv in observerList)
{
List<IEventObserver> list = kv.Value;
foreach (IEventObserver obj in list)
{
if (obj == removeObj)
{
list.Remove(obj);
break;
}
}
}
}

/// <summary>
/// 移除一个监听者
/// </summary>
/// <returns>The remove.</returns>
/// <param name="removeObj">需要移除的对象</param>
public static void Remove(IEventObserver removeObj)
{
if (EventManager.instance == null) return;
EventManager.instance.RemoveObj(removeObj);
}

/// <summary>
/// 监听者在这里注册
///
/// 注意这里形参的 params 就是 Java 的可变参数
/// </summary>
/// <param name="newobj">需要被注册的监听者</param>
/// <param name="eids">需要监听的事件列表.</param>
public static void Register(IEventObserver newobj, params EventID[] eids)
{
if (EventManager.instance == null) return;
foreach (EventID eid in eids)
{
EventManager.instance.RegisterObj(newobj, eid);
}
}
}
}

测试使用

自定义一个消息实体

这里自定义一个消息实体,用来封装数据,它要继承 EventData 基类

namespace EventFrame
{
public class Event1 : EventData
{
public int t1 { get; private set; }
public int t2 { get; private set; }
public string s1 { get; private set; }

public Event1(int t1, int t2, string s1) : base(EventID.EVENT_1)
{
this.t1 = t1;
this.t2 = t2;
this.s1 = s1;
}
}
}

编写一个监听者类

/// <summary>
/// 创建一个监听者测试
/// </summary>
public class EventTest : MonoBehaviour, IEventObserver
{

// Use this for initialization
private void Start()
{
// 注册自己进去监听 EVENT_1 和 EVENT_2 事件
EventManager.Register(this, EventID.EVENT_1, EventID.EVENT_2);
}

/// <summary>
/// 根据取得的事件类型进行不同的处理
/// </summary>
/// <param name="data"></param>
public void HandleEvent(EventData data)
{
switch (data.eid)
{
// 如果是 EVENT_1 则是自定义类型的事件
case EventID.EVENT_1:
Event1 eve = data as Event1;
Debug.Log("收到来自 event_1 事件");
Debug.Log($"t1:{eve.t1},t2:{eve.t2},s1:{eve.s1}");
break;
case EventID.EVENT_2:
Debug.Log("收到来自 event_2 事件");
break;
}
}

/// <summary>
/// 注意,要记得在该对象被销毁时把自己移除掉
/// </summary>
private void OnDestroy()
{
EventManager.Remove(this);
Debug.Log("当前对象已经移除出事件管理里面了~");
}
}

发送消息测试

这里使用 Unity 的 IMUI 来测试发送事件

/// <summary>
/// 这里演示了两种发送事件的方式,
/// 第一种是发送自己创建的 EventData 实体,这种方式的事件一般是有内容的(可能是自定义的子类)
/// 第二种是发送的一个只有 EventID 的空 EventData 对象
/// </summary>
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 150, 50), "send eve1"))
{
new Event1(1, 2, "测试发送了事件").Send();
}

if (GUI.Button(new Rect(10, 70, 150, 50), "send eve2"))
{
EventData.CreateEvent(EventID.EVENT_2).Send();
}
}

当分别点击2个按钮时我们可以在控制台看到相应的输出: